View Javadoc
1   package org.apache.maven.surefire.junitcore.pc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.testset.TestSetFailedException;
23  import org.junit.runner.Computer;
24  import org.junit.runner.Description;
25  
26  import java.util.Collection;
27  import java.util.TreeSet;
28  import java.util.concurrent.Callable;
29  import java.util.concurrent.ExecutionException;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.ScheduledExecutorService;
33  
34  import static java.util.concurrent.TimeUnit.NANOSECONDS;
35  
36  /**
37   * ParallelComputer extends JUnit {@link Computer} and has a shutdown functionality.
38   *
39   * @author Tibor Digana (tibor17)
40   * @see ParallelComputerBuilder
41   * @since 2.16
42   */
43  public abstract class ParallelComputer
44      extends Computer
45  {
46      private static final double NANOS_IN_A_SECOND = 1E9;
47  
48      private final ShutdownStatus shutdownStatus = new ShutdownStatus();
49  
50      private final ShutdownStatus forcedShutdownStatus = new ShutdownStatus();
51  
52      private final long timeoutNanos;
53  
54      private final long timeoutForcedNanos;
55  
56      private ScheduledExecutorService shutdownScheduler;
57  
58      public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds )
59      {
60          this.timeoutNanos = secondsToNanos( timeoutInSeconds );
61          this.timeoutForcedNanos = secondsToNanos( timeoutForcedInSeconds );
62      }
63  
64      protected abstract ShutdownResult describeStopped( boolean shutdownNow );
65  
66      abstract boolean shutdownThreadPoolsAwaitingKilled();
67  
68      protected final void beforeRunQuietly()
69      {
70          shutdownStatus.setDescriptionsBeforeShutdown( hasTimeout() ? scheduleShutdown() : null );
71          forcedShutdownStatus.setDescriptionsBeforeShutdown( hasTimeoutForced() ? scheduleForcedShutdown() : null );
72      }
73  
74      protected final boolean afterRunQuietly()
75      {
76          shutdownStatus.tryFinish();
77          forcedShutdownStatus.tryFinish();
78          boolean notInterrupted = true;
79          if ( shutdownScheduler != null )
80          {
81              shutdownScheduler.shutdownNow();
82              /**
83               * Clear <i>interrupted status</i> of the (main) Thread.
84               * Could be previously interrupted by {@link InvokerStrategy} after triggering immediate shutdown.
85               */
86              Thread.interrupted();
87              try
88              {
89                  shutdownScheduler.awaitTermination( Long.MAX_VALUE, NANOSECONDS );
90              }
91              catch ( InterruptedException e )
92              {
93                  notInterrupted = false;
94              }
95          }
96          notInterrupted &= shutdownThreadPoolsAwaitingKilled();
97          return notInterrupted;
98      }
99  
100     public String describeElapsedTimeout()
101         throws TestSetFailedException
102     {
103         final StringBuilder msg = new StringBuilder();
104         final boolean isShutdownTimeout = shutdownStatus.isTimeoutElapsed();
105         final boolean isForcedShutdownTimeout = forcedShutdownStatus.isTimeoutElapsed();
106         if ( isShutdownTimeout || isForcedShutdownTimeout )
107         {
108             msg.append( "The test run has finished abruptly after timeout of " );
109             msg.append( nanosToSeconds( minTimeout( timeoutNanos, timeoutForcedNanos ) ) );
110             msg.append( " seconds.\n" );
111 
112             try
113             {
114                 final TreeSet<String> executedTests = new TreeSet<String>();
115                 final TreeSet<String> incompleteTests = new TreeSet<String>();
116 
117                 if ( isShutdownTimeout )
118                 {
119                     printShutdownHook( executedTests, incompleteTests, shutdownStatus.getDescriptionsBeforeShutdown() );
120                 }
121 
122                 if ( isForcedShutdownTimeout )
123                 {
124                     printShutdownHook( executedTests, incompleteTests,
125                                        forcedShutdownStatus.getDescriptionsBeforeShutdown() );
126                 }
127 
128                 if ( !executedTests.isEmpty() )
129                 {
130                     msg.append( "These tests were executed in prior to the shutdown operation:\n" );
131                     for ( String executedTest : executedTests )
132                     {
133                         msg.append( executedTest ).append( '\n' );
134                     }
135                 }
136 
137                 if ( !incompleteTests.isEmpty() )
138                 {
139                     msg.append( "These tests are incomplete:\n" );
140                     for ( String incompleteTest : incompleteTests )
141                     {
142                         msg.append( incompleteTest ).append( '\n' );
143                     }
144                 }
145             }
146             catch ( InterruptedException e )
147             {
148                 throw new TestSetFailedException( "Timed termination was interrupted.", e );
149             }
150             catch ( ExecutionException e )
151             {
152                 throw new TestSetFailedException( e.getLocalizedMessage(), e.getCause() );
153             }
154         }
155         return msg.toString();
156     }
157 
158     private Future<ShutdownResult> scheduleShutdown()
159     {
160         return getShutdownScheduler().schedule( createShutdownTask(), timeoutNanos, NANOSECONDS );
161     }
162 
163     private Future<ShutdownResult> scheduleForcedShutdown()
164     {
165         return getShutdownScheduler().schedule( createForcedShutdownTask(), timeoutForcedNanos, NANOSECONDS );
166     }
167 
168     private ScheduledExecutorService getShutdownScheduler()
169     {
170         if ( shutdownScheduler == null )
171         {
172             shutdownScheduler = Executors.newScheduledThreadPool( 2 );
173         }
174         return shutdownScheduler;
175     }
176 
177     private Callable<ShutdownResult> createShutdownTask()
178     {
179         return new Callable<ShutdownResult>()
180         {
181             public ShutdownResult call()
182                 throws Exception
183             {
184                 boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout();
185                 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( false ) : null;
186             }
187         };
188     }
189 
190     private Callable<ShutdownResult> createForcedShutdownTask()
191     {
192         return new Callable<ShutdownResult>()
193         {
194             public ShutdownResult call()
195                 throws Exception
196             {
197                 boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout();
198                 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( true ) : null;
199             }
200         };
201     }
202 
203     private double nanosToSeconds( long nanos )
204     {
205         return (double) nanos / NANOS_IN_A_SECOND;
206     }
207 
208     private boolean hasTimeout()
209     {
210         return timeoutNanos > 0;
211     }
212 
213     private boolean hasTimeoutForced()
214     {
215         return timeoutForcedNanos > 0;
216     }
217 
218     private static long secondsToNanos( double seconds )
219     {
220         double nanos = seconds > 0 ? seconds * NANOS_IN_A_SECOND : 0;
221         return Double.isInfinite( nanos ) || nanos >= Long.MAX_VALUE ? 0 : (long) nanos;
222     }
223 
224     private static long minTimeout( long timeout1, long timeout2 )
225     {
226         if ( timeout1 == 0 )
227         {
228             return timeout2;
229         }
230         else if ( timeout2 == 0 )
231         {
232             return timeout1;
233         }
234         else
235         {
236             return Math.min( timeout1, timeout2 );
237         }
238     }
239 
240     private static void printShutdownHook( Collection<String> executedTests, Collection<String> incompleteTests,
241                                            Future<ShutdownResult> testsBeforeShutdown )
242         throws ExecutionException, InterruptedException
243     {
244         if ( testsBeforeShutdown != null )
245         {
246             for ( final Description test : testsBeforeShutdown.get().getTriggeredTests() )
247             {
248                 if ( test != null && test.getDisplayName() != null )
249                 {
250                     executedTests.add( test.getDisplayName() );
251                 }
252             }
253 
254             for ( final Description test : testsBeforeShutdown.get().getIncompleteTests() )
255             {
256                 if ( test != null && test.getDisplayName() != null )
257                 {
258                     incompleteTests.add( test.getDisplayName() );
259                 }
260             }
261         }
262     }
263 }